#include <QTcpSocket>
#include <QTimer>
#include <QHostAddress>
#include <QTextStream>
#include <QRegExp>

#include "user.h"
#include "audiosink.h"
#include "size.h"

#include <QDebug>

User::User(QTcpSocket* socket, User::userConfig config, QTextStream *out, QObject *parent) :
    QObject(parent)
{
    this->sock = socket;
    this->config = config;

    bytesCount = 0;
    lastBytesCount = 0;
    output = 0;
    //this is the estimated bitrate,  it will be user later to check if the user isn't trying to flood the server
    this->bitrate = config.format.getBitrate();

    this->out = out;
    if (config.banned) {
        kill("banned");
        return;
    }
    //dont touch, seriously !
    if (!config.bufferSize) {
        this->config.bufferSize = 75;
        //setting buffser size at 50ms
        //config.bufferSize = config.format.sampleRate() * config.format.sampleSize() / 8 * config.format.channelCount() * 0.05;
    }

    if (this->config.alias.isEmpty()) this->config.alias = sock->peerAddress().toString();

    //reconfigureSink();
    connect(sock,SIGNAL(readyRead()),this,SLOT(sockRead()));
    connect(sock,SIGNAL(disconnected()),this,SLOT(sockClose()));

    if (config.kickAfk) {
        say("afk protect is enabled, delay: " + QString::number(this->config.kickAfkTime) + "msecs");
        checkCount = 0;
        checker = new QTimer(this);
        checker->setInterval(this->config.kickAfkTime);
        connect(checker,SIGNAL(timeout()),this,SLOT(sockCheck()));
        checker->start();
    }
    if (config.muted) say("muted");
    say("connected");
    sendServerInfo();
}
User::~User() {
    sock->deleteLater();
    delete(output);
    delete(checker);
}
void User::reconfigureSink() {
    //config for the output
    AudioSink::userConfig audioConfig;
    audioConfig.format = &this->config.format;
    audioConfig.sinkName = config.alias;
    audioConfig.outputMode = config.currentMode;


    if (output != 0) output->deleteLater();

    if (this->config.muted) {
        audioConfig.outputMode = AudioSink::Mute;
    }
    //Tcp Relay ouput (may be rare)
    else if (this->config.tcpRelay.useRelay) {
        audioConfig.outputMode = AudioSink::Tcp;
        audioConfig.tcpConfig.host = config.tcpRelay.host;
        audioConfig.tcpConfig.port = config.tcpRelay.port;
    }

    output = new AudioSink("audio-transfer-server: " + config.alias,audioConfig,this);
}

void User::sockRead() {
    QByteArray data = sock->readAll();
    const int dsize = data.size();
    const int bsize = buffer.size();
    const int fsize = dsize + bsize;
    //if the first received data are config data: we dont send it to speakers to prevent that damn "PAF" noise !
    if (!bytesCount) {
        if (readUserConfig(&data)) {
            bytesCount += dsize;
            return;
        }
        else reconfigureSink();
    }
    //adding readed data size to the counter
    bytesCount += dsize;

    if (this->getSpeedAverage() > (quint64) this->bitrate) {
        say("warning: this user send too many data to the server");
    }
    if (config.muted) return;
    if (!output) return;
    if (!config.maxBufferSize) output->sinkStream(&data);
    else if (fsize < config.maxBufferSize) {
        buffer.append(data);
        if (fsize >= config.bufferSize) {
            output->sinkStream(&buffer);
            buffer.clear();
        }
    }
}
void User::sockClose() {
    say("disconnected, readed: " + Size::getWsize(bytesCount));
    emit(disconnected(this));
    this->deleteLater();
}

void User::sockError() {

}
void User::sockCheck() {
    if (((!bytesCount) && (!lastBytesCount)) || (bytesCount == lastBytesCount)) {
        kill("Afk (check: " + QString::number(checkCount) + ")");
        checker->stop();
        return;
    }
    lastBytesCount = bytesCount;
    checkCount++;
}
void User::say(const QString message) {
    if (!out) return;
    else if (!sock) return;
    else *out << QTime::currentTime().toString("HH:mm:ss") + " : [" + config.alias + "] -> " + message << endl;
}
void User::kill(const QString reason) {
    if (!reason.isEmpty()) say("kicked, reason: " + reason);
    else say("kicked, no reason");
    sock->disconnectFromHost();
    if (sock->isOpen()) sock->close();
    sock->deleteLater();
}
void User::setTextOutput(QTextStream *out) {
    this->out = out;
}
QHostAddress User::getHost() {
    return sock->peerAddress();
}
QString User::getAlias() {
    return config.alias;
}
void User::sendServerInfo() {
    QTextStream out(this->sock);

    out << "codec:audio/pcm channels:" << config.format.getChannelsCount();
    out << " sampleRate:" << config.format.getSampleRate();
    out << " sampleSize:" << config.format.getSampleSize();
    if (config.kickAfk) out << " afk:" << config.kickAfkTime;
    out << "\n\r";
}

bool User::readUserConfig(QByteArray *data) {
    say("trying to detect user config");
    QString rawUserConfig = QString(*data).split("\n").first();
    //this regex will detect if the received datas are configuration or sound
    QRegExp exp("[\\W]|[^\\d]|[^:]|[^\\s]",Qt::CaseSensitive,QRegExp::RegExp2);

    if (rawUserConfig.isEmpty()) say("no user config");
    else if (exp.indexIn(rawUserConfig)) say("no user config");
    else {
        say("readed user config: " + rawUserConfig);
        QStringList options = rawUserConfig.split(" ");
        QStringList::iterator i;
        for (i = options.begin();i != options.end();i++) {
            QString opt = *i;
            QStringList fields = opt.split(":");
            const int argc = fields.count();
            if (argc < 2) {
                if (!opt.isEmpty()) say("missing value for option: " + opt);
            }
            else {
                QString value;
                const QString key = fields.at(0);
                if (fields.count() >= 2) {
                    value = fields.at(1);
                }
                const int intVal = value.toInt();
                if (key == "samplerate") config.format.setSampleRate(intVal);
                else if (key == "samplesize") config.format.setSampleSize(intVal);
                else if (key == "name") config.alias = value;
                else if (key == "channels") config.format.setChannelCount(intVal);
                else if (key == "relay") {
                    if (argc < 3);
                    else if (!fields.at(2).toInt());
                    else {
                        config.tcpRelay.useRelay = true;
                        config.tcpRelay.host = value;
                        config.tcpRelay.port = fields.at(2).toInt();
                    }
                }
                else if (key == "soundModule") config.currentMode = AudioSink::getModeFromString(value);
                else say("unknow option: " + key + "value: " + value);
            }
        }
        say("end of user configuration received.");
        sock->write(QByteArray("configuration received.\n"));
        reconfigureSink();
        sendServerInfo();
        return true;
    }
    return false;
}
User::userConfig User::getConfig() {
    return config;
}
quint64 User::getReadedData() {
    return bytesCount;
}
quint64 User::getSpeedAverage() {
    if (!this->getReadedData()) return 0;
    return (quint64) (this->getReadedData() / this->connectTime.second());
}
